package astar;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;

import domain.Agent;
import domain.AgentDumbAuto;
import domain.AgentGreedy;
import domain.AgentHuman;
import domain.AgentSmart;
import domain.Board;
import domain.Debug;
import domain.Flag;
import domain.Game;
import domain.Square;
import domain.Game.action;
import domain.exceptions.CannotMoveThereException;

/**
 *
 * @author Shai Givony
 */
public class ANode implements Comparable<Object> {
	private Board					_board;
    private AgentSmart				_me;
    private Vector<Agent>           _agents;
    private Vector<Flag>            _flags;
    private int                     _h;         // the heuristic value
    private int                     _g;         // the effort value till now
    private int						_f;
    private LinkedList<action>   	_path;
    //private static int 	 			paneltyForDeath = -10;//TODO: change this
    
    //Constructor
    public ANode(Board board, AgentSmart me ,Vector<Agent> agents, Vector<Flag> flags, int g, LinkedList<action> path) {
    	// ANode receives all fields by reference, and clone them all.
    	cloneBoard(board);
    	//this._me			= new AgentSmart(me);
    	this._me			= me;		// this is fixed in cloneRestOfAgents();
        cloneRestOfAgents(agents);
        cloneFlags(flags);
        this._g      		= g;
        //this._g      		= me.getScore();
        this._h				= computeHeuristic();
        this._f				= computeF();
        this._path   		= clonePath(path);
    }
    
	public boolean isGoalNode() {
		return this._me.getSquare().isContainFlag();
	}

	private LinkedList<action> clonePath(LinkedList<action> path) {
		LinkedList<action> clonedPath = new LinkedList<action>();
		for (Iterator<action> iter = path.iterator(); iter.hasNext();) {
			action dir = (action) iter.next();
			clonedPath.addLast(dir);
		}
		return clonedPath;
	}

	private void addToPath(action dir){
		this._path.addLast(dir);
	}
	
    private void cloneFlags(Vector<Flag> flags) {
		this._flags = new Vector<Flag>();
		for (Iterator<Flag> iter = flags.iterator(); iter.hasNext();) {
			Flag flag = (Flag) iter.next();
			Square localSquare = this._board.getSquare(flag.getSquare().getRow(), flag.getSquare().getColumn()); 
			Flag localFlag = localSquare.getFlag();
			localFlag.setSquare(localSquare);
			_flags.addElement(localFlag);
		}
	}

	private void cloneBoard(Board board) {
		this._board = new Board(board);
	}

	private void cloneRestOfAgents(Vector<Agent> agents) {
    	this._agents	= new Vector<Agent>();
    	for (Iterator<Agent> iter = agents.iterator(); iter.hasNext();) {
			Agent otherAgent = (Agent) iter.next();
			Agent clonedAgent = null;
			if ( otherAgent == this._me ){
				clonedAgent = new AgentSmart((AgentSmart)otherAgent);
				this._me = (AgentSmart)clonedAgent;
			} else if ( otherAgent instanceof AgentSmart ){
				clonedAgent = new AgentSmart((AgentSmart)otherAgent);
			} else if ( otherAgent instanceof AgentDumbAuto ){
				clonedAgent = new AgentDumbAuto((AgentDumbAuto)otherAgent);
			} else if ( otherAgent instanceof AgentGreedy ){
				clonedAgent = new AgentGreedy((AgentGreedy)otherAgent);
			} else if ( otherAgent instanceof AgentHuman ){
				clonedAgent = new AgentHuman((AgentHuman)otherAgent);
			}
			//TODO: add CC to rest of agents and use it here
			this._agents.addElement(clonedAgent);
			//fixing clonedAgent reference to local copy of board.\
			//clonedAgent.getSquare().getRow();
			Square localSquare = _board.getSquare(clonedAgent.getSquare().getRow(),clonedAgent.getSquare().getColumn());
			clonedAgent.setSquare(localSquare);
			localSquare.setAgent(clonedAgent);

		}//end of for
	}

	public ANode simulateNode(action dir){
        // constructing the actual new node:
		ANode ans = new ANode(this._board, this._me, this._agents, this._flags, this._me.getScore(), this._path);
        ans.addToPath(dir);			//adding direction to the direction path
        
        // simulate I am moving 
        try {
        	ans.simulateMoveAgentMe(dir);
		} catch (CannotMoveThereException e) {
			Debug.println("Anode: "+e.getMessage());
		}
		
		// simulate Sentry is moving
        try {
        	//Agent sentry = getSentryFromAgents();
        	ans.simulateMoveAgentSentry(dir);
		} catch (CannotMoveThereException e) {
			Debug.println("Anode: "+e.getMessage());
		}
		
        return ans;
    }
    
    private Agent getSentry() {
		for (Iterator<Agent> iter = this._agents.iterator(); iter.hasNext();) {
			Agent agent = (Agent) iter.next();
			if ( agent != this._me && 
					( agent instanceof AgentDumbAuto  ||  agent instanceof AgentHuman )){
				return agent;
			}
		}
		Debug.println("No Sentry was Found!");
		return null;
	}

	private void simulateMoveAgentMe(action dir) throws CannotMoveThereException {
		this._me.act(dir, this._board, this._agents);
    	this._g = this._me.getScore();	// update g value
		if (_me.isFlagLatelyTaken()){
			////////////////////////////////////HERE/////////////////////////////////////
			this._g += Game.FLAG_VALUE;
			_me.setFlagLatelyTaken(false);
		}
		this._h = computeHeuristic();	// update h value
    	this._f = computeF();			// update f value
	}

	private void simulateMoveAgentSentry(action dir) throws CannotMoveThereException {
    	getSentry().play(this._board, this._agents, 0);
    	this._h = computeHeuristic();	// update h value
    	this._f = computeF();			// update f value
	}
	
	private int computeHeuristic(){
		int ans = 0;
		
		//here we will use BFS to estimate the distance to the nearest flag.
    	Board board = Game._board;
    	LinkedList<action> directionList = Bfs.pathToNearestFlag(_me.getSquare(), board);
    	//int panelty=isInLineOfFire();
    	
    	//int ans = directionList.size()-panelty;
    	if (this._me.isDead())
    		ans = directionList.size();
    	else
    		ans = directionList.size() - Game.FLAG_VALUE;
        return ans;
    }
    
    /**
     * the function check if you will die in the current location
     * @return panaltyForDeath if true, else return zero
     */
//    private int isInLineOfFire() {
//		for (Iterator<Agent> iterator = _agents.iterator(); iterator.hasNext();) {
//			Agent smith = (Agent) iterator.next();
//			if(!smith.equals(this._me)){//avoid calling to myself
//				int myLocationX=_me.getSquare().getColumn();
//				int myLocationY=_me.getSquare().getRow();
//				int smithLocationX=smith.getSquare().getColumn();
//				int smithLocationY=smith.getSquare().getRow();
//				if(myLocationX==smithLocationX&&noWall(myLocationY,smithLocationY, true,myLocationX)||myLocationY==smithLocationY&&noWall(myLocationX,smithLocationX,false,myLocationY)){
//					return paneltyForDeath;
//				}
//			}
//		}
//		return 0;
//	}
//    /**
//     * @param from: the coordinate to start looking from
//     * @param to: the coordinate to look until
//     * @param vetrical: a boolean parameter, if true the check will be performed up-down, else left-right
//     * @param cordinate: a location that the for will run on
//     * @return true if there is no wall between the (from, coordinate)\(coordinate, from) the
//     *		   to (to, coordinate)\(coordinate,to) [depending of vertical]. else retrun false.
//     */
//	private boolean noWall(int from, int to, boolean vertical,int coordinate) {
//		//small adjustment so we will scan from the left to right and up down. to prevent
//		//problem with the following for loops. 
//		int min=Math.min(from,to);
//		int max=Math.max(from,to);
//		from=min;
//		to=max;
//		
//		if(vertical){
//			for (int i = from; i < to; i++) {
//				if(_board.getSquare(i, coordinate) instanceof SquareWall ){
//					return false;
//				}
//			}
//		}else{//chack if there is a wall from "from" to "to" in horizontaly in the "cordination
//			for (int i = from; i < to; i++) {
//				if(_board.getSquare(coordinate, i) instanceof SquareWall ){
//					return false;
//				}
//			}
//		}
//		return true;
//	}//end of if wall

	private int computeF(){
        int ans = -1;
        //TODO: change here:
        ans = this._g + this._h;//computeHeuristic();
        //ans = this._me.getScore() + this._h;//computeHeuristic();
        
        return ans;
    }

    public int compareTo(Object o) {
        ANode other = (ANode)o;
        
        if ( this == other ){
            return 0;
        }
        
        //int thisF = this.computeF();
        //int otherF = other.computeF();
        
        //TODO check if the order is correct:
        if ( this._f > other._f )
        //if ( this._me.getScore()-this._f > other._f )
            return 1;
        else if ( this._f < other._f )
            return -1;
        else
            return 0;
    }

    public LinkedList<action> getPath() {
        return _path;
    }

    public Vector<Agent> getAgents() {
        return _agents;
    }

    public Vector<Flag> getFlags() {
        return _flags;
    }

    public int getG() {
        return _g;
    }
    
    //TODO: deeper check of equals is needed (for history)
    @Override
    public boolean equals(Object other){
    	if (!(other instanceof ANode)){
			return false;
		}
    	ANode otherANode = (ANode) other;
    	return getMe().isDead() == otherANode.getMe().isDead()						&&
    		   getSentry().isDead() == otherANode.getSentry().isDead()				&&
    		   getMe().getSquare().equals(otherANode.getMe().getSquare())			&&
    		   getSentry().getSquare().equals(otherANode.getSentry().getSquare());//	&&
    		   //getPath().equals(otherANode.getPath())								&&
    		   //this._f == otherANode._f;
    		   
//    		   this._flags.equals(otherANode._flags);
    }
    
//    /**
//     * 
//     * @param agents2: Vector of agents
//     * @param agents: Vector of agents
//     * @return true if the agents are the same in both vector, not necessarily the same order
//     */
//    private boolean sameAgents(Vector<Agent> agents2, Vector<Agent> agents) {
//		return agents.containsAll(agents2)&&agents2.containsAll(agents);
//	}
//
//	/**
//     * @return: true if the vector has the same flags not necessarily the same order
//     *          based on the isEquals of Flag.
//     */
//	private boolean sameFlags(Vector<Flag> flags2, Vector<Flag> flags) {
//		return flags.containsAll(flags)&&flags2.containsAll(flags);
//	}
//
//	private Board getBoard() {
//		return this._board;
//	}
//
	private Agent getMe() {
		return _me;
	}
	
	@Override
	public String toString() {
		String ans = "ANode: " + this._path + "  F=" + this._f + " G=" + this._g + " H=" + this._h;
		if (this._me.isDead())
			ans += " Dead";
		//ans += "\n       Sentry: " + this._agents.elementAt(0).getSquare();
		return ans;
	}
}
